package com.xyhy.starter.codegen;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.TypeUtil;
import com.jfinal.template.Engine;
import com.jfinal.template.Template;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.web.bind.annotation.RequestMapping;
import com.xyhy.core.base.enums.BaseTagEnum;
import com.xyhy.starter.codegen.model.EntityDataBean;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author robert
 * @since 2025-06-11
 */
public class UiGenerator {
  private static List<String> IGNORE_FIELDS = CollUtil.toList("serialVersionUID", "createBy", "updateBy", "updateTime");
  private static final Engine engine;
  private static final String tplName = "crud_vue3.tpl";

  static {
    URL resourceUrl = UiGenerator.class.getClassLoader().getResource(tplName);
    engine = Engine.create("myEngine");
    engine.setBaseTemplatePath(FileUtil.file(resourceUrl).getParentFile().getPath());
  }

  public static String generator(Class controller, Type entityType) {
    EntityDataBean dataBean = readEntityMetadata(controller, entityType);
    Map<String, Object> dataMap = BeanUtil.beanToMap(dataBean);

    Template template = engine.getTemplate(tplName);
    System.out.println(template.renderToString(dataMap));
    return template.renderToString(dataMap);
  }

  private static EntityDataBean readEntityMetadata(Class controller, Type entityType) {
    Class entityClass = TypeUtil.getClass(entityType);
    String name = entityClass.getSimpleName().toLowerCase();
    String title = Arrays.stream(entityClass.getAnnotations())
            .filter(x -> x instanceof ApiModel)
            .findFirst().map(x -> (ApiModel) x).map(x -> x.value())
            .orElse(null);

    List<EntityDataBean.Field> fields = readEntityFields(entityClass);
    boolean hasBooleanField = fields.stream().anyMatch(x -> x.getType().equals(Boolean.class));
    return new EntityDataBean()
            .setName(name)
            .setTitle(title)
            .setBaseUrl(getBaseUrl(controller))
            .setFields(fields)
            .setHasBooleanField(hasBooleanField);
  }

  private static List<EntityDataBean.Field> readEntityFields(Class entityClass) {
    return Arrays.stream(ReflectUtil.getFields(entityClass))
            .filter(x -> !IGNORE_FIELDS.contains(x.getName()))
            .map(x -> {
              EntityDataBean.Field data = new EntityDataBean.Field();
              data.setName(x.getName());
              data.setTitle(x.getAnnotation(ApiModelProperty.class).value());
              data.setType(x.getType());
              data.setTypeName(x.getType().getSimpleName());
              data.setNotNull(x.isAnnotationPresent(NotNull.class));
              data.setMaxLength(getFieldMaxLength(x));
              data.setIsForeignKey(x.getName().endsWith("Id"));
              data.setForeignDataName(getForeignDataName(x));
              data.setIsEnum(x.getType().isEnum());
              data.setIsTagEnum(x.getType().isEnum() && BaseTagEnum.class.isAssignableFrom(x.getType()));
              data.setColumnProp(getColumnProp(x));
              data.setIsTextArea(isTextArea(x, data.getMaxLength()));
              return data;
            }).collect(Collectors.toList());
  }


  private static String getBaseUrl(Class controller) {
    return Optional.ofNullable(controller)
            .map(x -> x.getAnnotation(RequestMapping.class))
            .map(x -> ((RequestMapping) x).value())
            .map(x -> x[0]).orElse(null);
  }

  private static Integer getFieldMaxLength(Field field) {
    if (!field.getType().equals(String.class)) return null;
    return Optional.ofNullable(field.getAnnotation(Size.class)).map(x -> x.max()).orElse(null);
  }

  private static String getColumnProp(Field field) {
    String name = field.getName();
    if (name.endsWith("Id")) {
      return name.substring(0, name.length() - 2) + "Name";
    } else if (field.getType().isEnum() && !BaseTagEnum.class.isAssignableFrom(field.getType())) {
      return name + "Text";
    }
    return name;
  }

  private static String getForeignDataName(Field field) {
    String name = field.getName();
    if (name.endsWith("Id")) {
      return name.substring(0, name.length() - 2) + "Options";
    }
    return null;
  }

  private static boolean isTextArea(Field field, Integer maxLength) {
    if (!field.getType().equals(String.class)) return false;
    return Optional.ofNullable(maxLength).map(x -> x >= 255).orElse(false);
  }

}
